package org.wheel.plugins.starter.jdbc.pool.config;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import org.wheel.module.core.util.ClassUtil;
import org.wheel.plugins.starter.autoconfigure.DynamicDataSourceProperties;
import org.wheel.plugins.starter.jdbc.pool.annon.*;
import org.wheel.plugins.starter.jdbc.pool.bean.XmlSqlMeta;
import org.wheel.plugins.starter.jdbc.pool.dao.BaseDaoImpl;
import org.wheel.plugins.starter.jdbc.pool.dao.IBaseDao;
import org.wheel.plugins.starter.jdbc.pool.util.LightXmlSqlBuilder;
import org.wheel.plugins.toolkit.jdbc.pool.util.SqlExecutionUtil;
import org.wheel.plugins.toolkit.sql.core.GlobalConfig;
import org.wheel.plugins.toolkit.util.sql.visitor.SqlParameterReplacementHandler;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class DaoProxy implements InvocationHandler {

    // 直接使用 LogFactory 创建日志对象，Hutool 会自动检测项目中的日志框架（优先级：Slf4j > Log4j > Logback > JDK Logging）
    private static final Log log = LogFactory.get();

    // =======================  构造参数  ========================
    // 1. 获取接口
    private final Class<?> daoInterface;
    private final DaoConfigProperties daoConfig;
    private final DynamicDataSourceProperties conn;

    // 构造函数
    public DaoProxy(Class<?> daoInterface, DynamicDataSourceProperties conn, DaoConfigProperties daoConfig) {
        log.trace("DaoProxy for: " + daoInterface.getName());
        this.daoInterface = daoInterface;
        this.conn = conn;
        if(this.conn != null && StrUtil.isBlank(this.conn.getName())){
            this.conn.setName(this.conn.getDefaultName());
        }
        this.daoConfig = daoConfig;
        this.entityClass = getGenericType(daoInterface);
        if(this.entityClass != null) {
            this.createBaseDao(entityClass);
        }
    }

    // =======================  内部缓存  ========================
    // IBaseDao缓存
    private static final ConcurrentHashMap<Class<?>, IBaseDao<?>> baseDaoCache = new ConcurrentHashMap<>();
    // 方法执行结果缓存
    private final Map<Method, Object> localCache = new ConcurrentHashMap<>();
    // SQL缓存 key: 方法 value: <type, SQL> type=/select/insert/update/delete
//    private static final Map<Method, Pair<String, String>> SQL_CACHE = new ConcurrentHashMap<>();

    private Class<?> entityClass;


    // =======================  代理方法  ========================
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        // 反馈结果
        Object result = null;

        // 1. 判断是否是IBaseDao内的方法, 如果是则调用 IBaseDao
        if(this.entityClass != null && this.getBaseDao(this.entityClass) != null){
            boolean isInDao = isMethodInInterface(IBaseDao.class, method.getName(), method.getParameterTypes());
            if(isInDao){
                IBaseDao<?> baseDao = this.getBaseDao(this.entityClass);
                result = method.invoke(baseDao, args);
                return result;
            }
        }

        /******************************   以上是接口方法，下面是带注解的自定义xml方法 *****************************/
        // 1. 取 SQL
        String sql = null;
        // 最终执行的参数
        LinkedHashMap<String, Object> paramLinkedMap = new LinkedHashMap<>();
        String sqlId = daoInterface.getName() + "." + method.getName();
        Pair<String, String> sqlPair = sqlExtraction(method, sqlId);
        String xmlSql = sqlPair.getValue();
        String sqlType = sqlPair.getKey();
        Parameter[] parameters = method.getParameters();
        /*
         * 如果是用xml或者select自定义的sql，必须用@Param注解，提取参数名称，否则会报错
         * 因为没办法提取到参数名称，所以只能用注解强制获取了。
         */
        if(parameters != null && parameters.length > 0){
            // 处理注解的参数
            Map<String, Object> params = annonParamHandler(method, args);

            XmlSqlMeta meta = null;
            try {
                meta = LightXmlSqlBuilder.build(xmlSql, params);
            } catch (Exception e) {
                throw new RuntimeException("方法 " + method.getName() + " 的参数类型错误。请检查参数类型是否正确。");
            }

            sql = meta.sql();
            log.trace("SQL: " + sqlType + " " + sqlId + " " + sql);

            // 从 SQL 中提取参数名称
            List<Pair<String, String>> pairs = new ArrayList<>();
            SqlParameterReplacementHandler.extractNamedParameters(sql, pairs);
            // 默认，参数名采用 SQL 中参数名
            List<String> paramNames = pairs.stream().map(Pair::getValue).toList();
            // SQL 处理
            sql = sqlHandler(sql, pairs);

            // 3. 处理参数
            paramsHandler(method, args, paramLinkedMap, paramNames);

        }else{
            sql = xmlSql;
        }
        // 4. 执行 SQL
        result = sqlExecute(sqlType, method, sql, paramLinkedMap);

        // 返回结果
        return result;
    }

    /**
     * 注解参数处理
     * @param method  方法
     * @param args 参数
     * @return
     */
    private Map<String, Object> annonParamHandler(Method method, Object[] args){
        Parameter[] parameters = method.getParameters();
        if(ArrayUtil.isEmpty(parameters)){
            return Map.of();
        }
        // 如果只有一个参数，且是Map，则直接返回
        if(parameters.length == 1 && Map.class.isAssignableFrom(parameters[0].getType())){
            return (Map<String, Object>) args[0];
        }
        // 必须全部都有param注解，否则抛出异常
        boolean isUseParamAnnotation = Stream.of(parameters)
                .allMatch(parameter -> parameter.isAnnotationPresent(Param.class));
        // 如果只有一个参数，且是对象，可以不用注解,如果用注解了，跳过当前判断
        if(parameters.length == 1
                && !Collection.class.isAssignableFrom(parameters[0].getType())
                && !Array.class.isAssignableFrom(parameters[0].getType())
                && !ClassUtil.isBasicType(parameters[0].getType())
                && !isUseParamAnnotation){
            return BeanUtil.beanToMap(args[0]);
        }
        // 多于一个参数的，必须全部有注解
        if(!isUseParamAnnotation){
            throw new RuntimeException("方法 " + method.getName() + " 的参数必须用 @Param 注解。注解路径：org.wheel.plugins.starter.jdbc.pool.annon.Param");
        }

        Map<String, Object> params = new LinkedHashMap<>();
        for (int i = 0; i < parameters.length; i++) {
            Param param = parameters[i].getAnnotation(Param.class);
            if (param != null) {
                params.put(param.value(), args[i]);
            }
        }

        return params;
    }

    /**
     * 最后SQL执行
     * @param sqlType SQL类型：select/insert/update/delete
     * @param method  方法
     * @param sql 执行 SQL
     * @param paramLinkedMap 参数
     * @return 执行结果
     */
    private Object sqlExecute(String sqlType, Method method, String sql, LinkedHashMap<String, Object> paramLinkedMap){
        Object result = null;
        if ("select".equalsIgnoreCase(sqlType)) {
            Type returnType = method.getGenericReturnType();
            // 如果返回类型是集合
            if (!isSingleObject(method)) {
                // 获取返回对象的泛型类
                Class<?> clazz = extractClass(returnType);
                if (clazz != null) {
                    result = SqlExecutionUtil.getInstance().executeSqlQueryForList(conn, sql, paramLinkedMap, GlobalConfig.getSqlModel(), clazz);
                } else {  // 兜底方法，返回 List<Map<String, Object>>
                    result = SqlExecutionUtil.getInstance().executeSqlQueryForList(conn, sql, paramLinkedMap, GlobalConfig.getSqlModel());
                }
            } else {
                // 如果返回类型是实体类
                result = SqlExecutionUtil.getInstance().executeSqlQueryForObject(conn, sql, paramLinkedMap, GlobalConfig.getSqlModel(), extractClass(returnType));
            }
        } else {
            // update, insert, delete 统一用 executeSqlUpdate 方法
            result = SqlExecutionUtil.getInstance().executeSqlUpdate(conn, sql, paramLinkedMap, GlobalConfig.getSqlModel());
        }
        return result;
    }

    /**
     * SQL 提取
     * key: sql; value: sqlType
     * @return
     */
    private Pair<String, String> sqlExtraction(Method m, String sqlId){

            // 提取自定义 xml SQL
            String xml = XmlMapperLoader.getXmlSql(sqlId);
            if (xml != null) {
                String type = XmlMapperLoader.getXmlSqlType(sqlId);
                return Pair.of(type, xml);
            }
            // 回退注解
            Select sel = m.getAnnotation(Select.class);
            if (sel != null && sel.value().length > 0) {
                return Pair.of("select", sel.value()[0]);
            }
            Insert ins = m.getAnnotation(Insert.class);
            if (ins != null && ins.value().length > 0) {
                return Pair.of("insert", ins.value()[0]);
            }
            Update upd = m.getAnnotation(Update.class);
            if (upd != null && upd.value().length > 0) {
                return Pair.of("update", upd.value()[0]);
            }
            Delete del = m.getAnnotation(Delete.class);
            if (del != null && del.value().length > 0) {
                return Pair.of("delete", del.value()[0]);
            }
            throw new IllegalStateException("no SQL for " + m);
    }


    // 判断返回类型是否是集合
    public boolean isCollection(Method m) {
        return Collection.class.isAssignableFrom(m.getReturnType());
    }

    // 获取返回对象是否数组
    public boolean isArray(Method m) {
        return m.getReturnType().isArray();
    }
    // 判断返回对象是否是单个对象
    public boolean isSingleObject(Method m) {
        return !isCollection(m) && !isArray(m);
    }

    public Class<?> extractClass(Type type) {
        if (type instanceof Class) {          // 原始类型或没有泛型
            return (Class<?>) type;
        }
        if (type instanceof ParameterizedType) {
            // 只取第一个实参，按需递归
            Type raw = ((ParameterizedType) type).getActualTypeArguments()[0];
            return extractClass(raw);
        }
        if (type instanceof GenericArrayType) {
            Type comp = ((GenericArrayType) type).getGenericComponentType();
            return Array.newInstance(extractClass(comp), 0).getClass();
        }
        if (type instanceof WildcardType) {
            Type[] upper = ((WildcardType) type).getUpperBounds();
            return extractClass(upper[0]);
        }
        if (type instanceof TypeVariable) {
            Type[] upper = ((TypeVariable<?>) type).getBounds();
            return extractClass(upper[0]);
        }
        throw new IllegalArgumentException("无法解析的 Type: " + type);
    }

    /**
     * 将 SQL 语句进行处理成 :propertiesName 占位符模式
     * @param sql
     * @return
     */
    private String sqlHandler(String sql, List<Pair<String, String>> pairs){
        final String[] result = {sql};
        pairs.forEach(pair -> {
            result[0] = result[0].replace(pair.getKey(), ":" + pair.getValue());
        });
        return result[0];
    }

/**
 * 参数处理
 * @param method  方法
 * @param args 参数
 * @param paramLinkedMap 最终返回的参数
 * @param paramNames 从SQL中提取的参数名称
 */
private void paramsHandler(Method method, Object[] args, LinkedHashMap<String, Object> paramLinkedMap, List<String> paramNames) {
        if(args != null) {
            if (args.length == 1) {
                if (ClassUtil.isBasicType(args[0].getClass())) {
                    // 如果有参数注解，优先用参数注解的名称
                    Param param = method.getParameters()[0].getAnnotation(Param.class);
                    if (param != null && StrUtil.isNotBlank(param.value())) {
                        paramLinkedMap.put(param.value(), args[0]);
                    } else {
                        if (CollUtil.isNotEmpty(paramNames)) {
                            paramLinkedMap.put(paramNames.get(0), args[0]);
                        } else {
                            // 兜底写法，采用 arg0 参数名
                            String name = method.getParameters()[0].getName();
                            paramLinkedMap.put(name, args[0]);
                        }
                    }
                } else {
                    Map<String, Object> paramMap = BeanUtil.beanToMap(args[0]);
                    paramLinkedMap.putAll(paramMap);
                }
            } else {
                for (int i = 0; i < args.length; i++) {
                    paramHandler(method, args[i], i, paramLinkedMap, paramNames);
                }
            }
        }
    }

    private void paramHandler(Method method, Object arg, int index, LinkedHashMap<String, Object> paramLinkedMap, List<String> paramNames){
        // 优先处理参数注解
        Param param = method.getParameters()[index].getAnnotation(Param.class);
        if(param != null && StrUtil.isNotBlank(param.value())){
            DataHandler(param.value(), arg, paramLinkedMap);
        }else {
            if(CollUtil.isNotEmpty(paramNames)){
                DataHandler(paramNames.get(index), arg, paramLinkedMap);
            } else {
                // 兜底写法，采用 arg0 参数名
                String name = method.getParameters()[index].getName();
                DataHandler(name, arg, paramLinkedMap);
            }
        }
    }

    private void DataHandler(String name, Object arg, LinkedHashMap<String, Object> paramLinkedMap){
        if(ClassUtil.isBasicType(arg.getClass())){
            paramLinkedMap.put(name, arg);
        }else{
            Map<String, Object> paramMap = BeanUtil.beanToMap(arg);
            paramMap.keySet().forEach(key ->
                    paramLinkedMap.put(StrUtil.join(StrUtil.DOT, StrUtil.blankToDefault(name, ""), key), paramMap.get(key))
            );
        }
    }

    /**
     * 获取接口泛型参数
     * 针对继承了IBaseDao的接口
     * @param interfaceClass 接口类
     * @return 泛型参数类
     */
    public Class<?> getGenericType(Class<?> interfaceClass) {
        // 获取 MyInterface 的所有接口
        Class<?>[] interfaces = interfaceClass.getInterfaces();
        if (interfaces == null || interfaces.length == 0 || !Arrays.asList(interfaces).contains(IBaseDao.class)) {
            return null;
        }
        Class<?> result = null;
        Type[] genericInterfaces = interfaceClass.getGenericInterfaces();
        for (Type genericInterface : genericInterfaces) {
            if (genericInterface instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
                Type[] typeArguments = parameterizedType.getActualTypeArguments();
                for (Type typeArgument : typeArguments) {
                    if (typeArgument instanceof Class clazz) {
//                            System.out.println("Type argument: " + typeArgument.getTypeName());
                        result = clazz;
                        break;
                    }
                }
            }
        }
        return result;
    }


    /**
     * 判断方法和参数是否属于接口中的方法
     * @param interfaceClass 接口类
     * @param methodName 当前要判断的方法名
     * @param parameterTypes 当前要判断的参数类型
     * @return
     */
    public static boolean isMethodInInterface(Class<?> interfaceClass, String methodName, Class<?>... parameterTypes) {
        // 获取接口中所有方法
        Method[] methods = interfaceClass.getMethods();

        // 遍历接口中的方法
        for (Method method : methods) {
            // 比较方法名
            if (method.getName().equals(methodName)) {
                // 获取方法的参数类型
                Class<?>[] methodParameterTypes = method.getParameterTypes();
                // 比较参数类型
                if (methodParameterTypes.length == parameterTypes.length) {
                    boolean match = true;
                    for (int i = 0; i < parameterTypes.length; i++) {
                        if (methodParameterTypes[i] != parameterTypes[i]) {
                            match = false;
                            break;
                        }
                    }
                    if (match) {
                        return true; // 找到匹配的方法
                    }
                }
            }
        }
        return false; // 没有找到匹配的方法
    }

    // 创建 IBaseDao
    public <T> IBaseDao<T> createBaseDao(Class<T> entityClass) {
        return (IBaseDao<T>) baseDaoCache.computeIfAbsent(entityClass,
                k -> new BaseDaoImpl<>((Class<T>) k, conn));
    }

    // 获取 IBaseDao
    public <T> IBaseDao<T> getBaseDao(Class<T> entityClass) {
        return (IBaseDao<T>) baseDaoCache.get(entityClass);
    }
}
